
package algoritmogenetico;

import java.util.*; 
/* Esta clase corresponde a la población de todos los individuos del algoritmo genético.
 * 
 * Tiene como ATRIBUTOS:
 *  ** Un vector con entradas tipo Individuos.
 *  ** El número de individuos de la población.
 *  ** La longitud de los genomas
 *  ** Número de bits para enteros
 *  ** Número de bits para decimales
 *  ** Número de variables por genoma
 *  ** La probabilidad de CrossOver
 *  ** La probabilidad de mutación
 *  ** Un generador de números aleatorios
 * 
 * Tiene como MÉTODOS:
 *  ** Genera población aleatoria
 *  ** Varios métodos para imprimir la población o parte de ella
 *  ** Fitness de la población (con normalización de linear offset)
 *  ** CDF del fitness
 *  ** Crossover
 *  ** Mutación puntual
 *  ** Selección proporcional
 *  ** Un método Quicksort para ordenar los individuos
 */
public class Poblacion {
    public Individuo[] individuos;
    public int totalInd;
    public int longitudGen;
    public int bitsEnt;
    public int bitsDec;
    public int numVar;
    public double probXOver;
    public double probMuta;
    public Random alea;
    
    //Inicializa un objeto población pero sin los individuos
    public Poblacion(int num, int bE, int bD, int nV,double pxo, double pM, Random a){
        totalInd = num;
        probXOver = pxo;
        individuos = new Individuo[num];
        probMuta = pM;
        bitsEnt = bE;
        bitsDec = bD;
        numVar = nV;
        longitudGen = (bitsEnt + bitsDec + 1)*numVar;
        alea = a;
    }
    
    //Inicializa un objeto población copiando a la población que se da como parámetro
    public Poblacion(Poblacion pob2){
        totalInd = pob2.totalInd;
        probXOver = pob2.probXOver;
        individuos = new Individuo[totalInd];
        for(int i = 0; i<totalInd; i++){
             individuos[i] = new Individuo(pob2.individuos[i]);
        }
        probMuta = pob2.probMuta;
        bitsEnt = pob2.bitsEnt;
        bitsDec = pob2.bitsDec;
        numVar = pob2.numVar;
        longitudGen = (bitsEnt + bitsDec + 1)*numVar;
        alea = pob2.alea;
    }
    
    //Genera aleatoriamente a los individuos de la población
    public void CreaPoblacionInicial(){
        individuos = new Individuo[totalInd];
        for(int i = 0; i< totalInd; i++){
            individuos[i] = new Individuo(bitsEnt, bitsDec, longitudGen,numVar, probMuta,alea);
            individuos[i].CreaAleatorio();
        }
    }
    
    //Imprime a todos todos los individuos de la población y sus atributos
    public void ImprimePoblacion(){
        for(int i = 0; i<totalInd; i++){
            System.out.println("");
            System.out.println("Individuo "+i+": ");
            individuos[i].ImprimeTodo();
        }
    }
    
    //Imprime a los individuos de la población, sus atributos y el valor de las 
    //variables que están codificadas en su genoma
    public void ImprimePoblacionConValores(){
            for(int i = 0; i<totalInd; i++){
            System.out.println("");
            System.out.println("Individuo "+i+": ");
            individuos[i].ImprimeTodo();
            individuos[i].ImprimeValores();
        }
    }
    
    //Imprime los genomas de la población
    public void ImprimeGenomas(){
        for(int i = 0; i<totalInd; i++){
            System.out.println("");
            System.out.println("Individuo "+i+": ");
            individuos[i].ImprimeGenoma();
        }
    }
    
    //Imprime el fitness del mejor individuo de la población y los valores de las
    //variables codificadas en su genoma
    public void ImprimeOptimo(){
        System.out.println("\nResultado: ");
        individuos[0].ImprimeResultado();
    }
    
    public String RegresaOptimoString(){
        return individuos[0].RegresaOptimoString();
       
    }
    
    //Muta a todos los individuos de la población
    public void Muta(){
        for(int i = 0; i<totalInd; i++){
            individuos[i].Muta();
        }
    }
    
    /*Este método va creando los individuos de la población 2 a partir de hacer 
     * el crossover con los elementos de la población 1.
     */
    public void Xover(Poblacion pob2){
        for(int i = 0; i<totalInd; i+=2){
            //Escoge aleatoriamente a dos individuos de la población a la que pertenece el método
            int escogeInd1 = (int)Math.floor(Math.random()*totalInd);
            int escogeInd2 = (int)Math.floor(Math.random()*totalInd);
            int puntoCruce = (int)Math.floor(Math.random()*longitudGen);
            //Inicializa dos indiviudos en la nuevo población que entra como parámetro
            pob2.individuos[i]=new Individuo(bitsEnt, bitsDec, longitudGen, numVar,probMuta,alea);
            pob2.individuos[i+1]=new Individuo(bitsEnt, bitsDec, longitudGen, numVar,probMuta,alea);
            //Si gana el volado, entonces inicializa el genoma con el crossover.
            if(probXOver > alea.nextDouble()){
                pob2.individuos[i]=individuos[escogeInd1].XOver(individuos[escogeInd2],puntoCruce);
                pob2.individuos[i+1]=individuos[escogeInd2].XOver(individuos[escogeInd1],puntoCruce);
            }
            //Si pierde el volado, copia tal cual el genoma de los elementos escogidos.
            else{
                pob2.individuos[i].CopiaGenoma(individuos[escogeInd1]);
                pob2.individuos[i+1].CopiaGenoma(individuos[escogeInd2]);
            }
            
        }
        
    }
    
    //Método que calcula el fitness 
    public void Fitness(int opcion){
            for(int i = 0; i < totalInd; i++){
                individuos[i].Fitness(opcion);
            }    
    }
    
    //Función que encuntra el mínimo fitness y regresa su valor absolutizado
    public double MinimoFitnessAbs(){
        double min;
        min = individuos[0].fitness;
        for(int i = 0; i< totalInd; i++){
            if(min > individuos[i].fitness){
                min = individuos[i].fitness;
            }
        }
        min = Math.abs(min);
        return min;
    }
    
    //Función que calcula el promedio de los fitnesses absolutizados
    public double PromDeAbsolutos(){
        double prom = 0;
        for(int i = 0; i<totalInd; i++){
            prom = prom + Math.abs(individuos[i].fitness);
        }
        prom = prom/totalInd;
        return prom;
    }
    
    //Función que suma el total de los fitnesses normalizados
    public double SumaTotal(){
        double total = 0;
        for(int i = 0; i < totalInd; i++){
            total = individuos[i].fitnessNorm + total;
        }
        return total;
    }
    
    //Método que normaliza el fitness de los individuos de la población
    public void NormalizaFitness(int normaliza){
        if(normaliza == 1){ //Opción 1 == Linear Offset
            double min, prom, total;
            min = MinimoFitnessAbs();
            prom = PromDeAbsolutos();
            for(int i = 0; i<totalInd; i++){
                individuos[i].LinearOffset(min,prom);
            }
            total = SumaTotal();
            for(int i = 0; i<totalInd; i++){
                individuos[i].CDF(total);
            }
        }
        
    }
    
    //Método que hace la selección proporcional.
    //Aquí la población que entra como parámetro es de donde se seleccionan los individuos.
    //La población a la que se le aplica el método es la donde guardaremos los individuos seleccionados.
    public void SeleccionProporcional(Poblacion pob2){
        boolean seleccionado;
        double aleatorio;
        int j;
        //Elitismo de 1 individuo.
        individuos[0]=new Individuo(pob2.individuos[0]);
        //Seleccionar al resto de la población
        for(int i = 1; i < totalInd; i++){
            aleatorio = alea.nextDouble();
            seleccionado = false;
            j = 0;
            while(!seleccionado){
                //Revisamos si el número aleatorio está en el rango del individuo[j]
                if(pob2.individuos[j].Selecciona(aleatorio)){
                    //Si sí, entonces lo seleccionamos y lo copiamos a la nueva población
                    individuos[i]= new Individuo(pob2.individuos[j]);
                    seleccionado = true;
                }
                //Si no, entonces nos movemos al individuo siguiente.
                else{
                    j++;
                }
            }
        }
    }
    
    //Método que ordena al vector de individuos en orden descendente de acuerdo 
    //a su fitness normalizado. Después de que están en orden, va sumando los 
    //fitnesses para obtener la CDF.
    public void OrdenaPorCDF(){
       QuickSort(0,totalInd-1);
       double acumulaB, acumulado = 0; 
       for(int i = 0; i < totalInd; i++){
           acumulaB = individuos[i].AcumulaCDF(acumulado);
           acumulado = acumulaB;
       }
    }
    
    //Método que ordena a los elementos mayores que el elemento del centro del lado izquierdo
    //y a los elementos menores que el elemento del centro del lado derecho.
    int Particion(int izq, int der){
      int i = izq, j = der;
      Individuo temp;
      Individuo pivote = individuos[(int)((izq + der)/2)];
      while (i <= j) {
            while (individuos[i].Compara(pivote) > 0){ //Estoy arreglando de mayor a menor CDF
                  i++;}
            while (individuos[j].Compara(pivote)<0){//De nuevo, los chicos al final.
                  j--;}
            if (i <= j) {
                  temp = new Individuo(individuos[i]);
                  individuos[i] = new Individuo(individuos[j]);
                  individuos[j] = new Individuo(temp);
                  i++;
                  j--;
            }
      }
      return i;
    }
 
    //Método que ordena los elementos de un vector llamando a Partición
    public void QuickSort(int izq, int der){
      int indice = Particion(izq, der);
      if (izq < indice - 1){
            QuickSort(izq, indice-1);
      }
      if (indice < der){
            QuickSort(indice, der);
      }
    }

}
